#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#define PROMPT_STRING "USH2>>"
#define QUIT_STRING "exit"
#define FFLAG (O_WRONLY | O_CREAT | O_TRUNC)
#define FMODE (S_IRUSR | S_IWUSR)
#define MAX_STRING 128

void executecmd(char *incmd); //명령어의 실행을 관장한다 
void executeredirect(char *s, int in, int out); //리다이렉션을 처리한다 
int makeargv(const char *s, const char *delimiters, char ***argvp); //인자값을 처리한다 
int parseandredirectin(char *s); //<의 리다이렉션을 처리 
int parseandredirectout(char *s); //>의 리다이렉션을 처리 
static void perror_exit(char *s);
void history_out(char *incmd); // 명령을 히스토리에 저장한다 
void history_print(); //히스토리를 출력해준다 
char* history_cmd(int num,char *buffer);//해당명령어를 가져오기 
char* cut_string(int start,int end,char* str ,char* buffer);//문자열 자르기용 함수 
void command(char *inbuf);//입력받은 값에 대해서 처리해준다 



int main(void){
    char inbuf[MAX_CANON];

    for(;;){
        if(fputs(PROMPT_STRING,stdout) == EOF)
            continue;
        if(fgets(inbuf,MAX_CANON,stdin) == NULL)
            continue;
        inbuf[strlen(inbuf)-1] = '\0';
        if(strncmp(inbuf,QUIT_STRING,4) == 0) //exit일경우 종료 시키기 
            exit(1);
        history_out(inbuf);
        command(inbuf);
    }
   return 0;
}

void command(char *cmds){
    int len;
    int flag=0;
    pid_t childpid;
    len = strlen(cmds);


    //!!에 대한 처리 
    if(strncmp(cmds,"!!",2) == 0){ //엘리어스의 처리 
        char tmp2[128];//임시용 변수 
        history_cmd(0,tmp2);
        flag=2;
    }

    //히스토리 !숫자 형태로 실행 
    if(strncmp(cmds,"!",1) == 0 && flag != 2){
        char tmp[128];//임시용 변수 
        char tmp2[128];//임시용 변수 
        cut_string(1,strlen(cmds),cmds,tmp);
        int num=atoi(tmp);
        //printf("%s \n" , history_cmd(num,tmp2));
        history_cmd(num,tmp2);
        flag=1;
    }
    if(strncmp(cmds,"alias",5)==0){
        printf("alias\n");
        flag=1;
    }

    //히스토리의 처리 
    if(strncmp(cmds,"history",7) == 0){
        history_print();
        flag=1;
    }


    if(flag == 0){
        if(cmds[len -1] == '\n')
            cmds[ len - 1 ] = 0;
        if((childpid = fork()) == -1)
            perror("Failed to fork child");
        else if(childpid == 0){
            executecmd(cmds);
            exit(1);
            //return 1;
        }else{
            wait(NULL);
        }
    }
}

//명령어를 .bash_history파일에 저장 
void history_out(char *incmd){
    FILE *out;
    out = fopen(".bash_history","a");
    if(strlen(incmd) > 0 && strncmp(incmd,"!",1) != 0){
        fprintf(out,"%s\n",incmd);
    }
    fclose(out);
}

//히스토리를 출력해준다 
void history_print(){
    FILE *in;
    int i;
    in = fopen(".bash_history","rt");
    char buffer[128];
    if(in != NULL){
        for(i=1;NULL != fgets(buffer,128,in);i++)
            printf("%d %s",i,buffer);
        fclose(in);
    }
    printf("\n");
}

//해당 명령에 해당하는 명령어 리턴 
char* history_cmd(int num,char *buffer){
    FILE *in;
    int i;
    in = fopen(".bash_history","rt");
    char buffer_tmp[MAX_STRING];
    buffer[0]='\0';
    if(in != NULL){
        for(i=1;NULL != fgets(buffer_tmp,128,in);i++)
        {
            if(i==num){
                break;
            }
        }
        fclose(in);
    }
    //executecmd(buffer_tmp);
    command(buffer_tmp);
    strcpy(buffer,buffer_tmp);
    return buffer;
}

static void perror_exit(char *s){
    perror(s);
    exit(1);
}

//명령어의 실행을 관장한다 
void executecmd(char *cmds){
    int child;
    int count;
    int fds[2];
    int i;
    int flag =0;//내부 명령어 여부를 나타내는 플레그 
    char **pipelist;
    //flag가 0일때는 내부명령어가 아니므로 명령어를 처리 
    if(flag == 0){
        count = makeargv(cmds, "|",&pipelist);
        if(count <= 0){
            fprintf(stderr,"Failed to find any commands\n");
            exit(1);
        }
        for(i=0;i<count -1;i++){
            if(pipe(fds) == -1)
                perror_exit("Failed to create pipes");
            else if((child = fork()) == -1)
                perror_exit("Failed to create process to run command");
            else if(child){
                if(dup2(fds[1],STDOUT_FILENO) == -1)
                    perror_exit("Failed to connect pipeline");
                if(close(fds[0]) || close(fds[1]))
                    perror_exit("Failed to close needed files");
                executeredirect(pipelist[i],i==0,0);
                exit(1);
            }
            if(dup2(fds[0],STDIN_FILENO) == -1)
                perror_exit("Failed to connect last component");
            if(close(fds[0]) || close(fds[1]))
                perror_exit("Failed to do final close");
        }
        executeredirect(pipelist[i],i==0,1);
    }
    exit(1);
}


void executeredirect(char *s, int in, int out){
    char **chargv;
    char *pin;
    char *pout;

    if(in && ((pin = strchr(s,'<')) != NULL) && out && ((pout = strchr(s,'>')) != NULL) && (pin > pout)){
        if(parseandredirectin(s) == -1){
            perror("Failed to redirect input");
            return ;
        }
        in = 0;
    }

    if(out && (parseandredirectout(s) == -1))
        perror("Failed to redirect output");
    else if(in && (parseandredirectin(s) == -1))
        perror("Failed to redirect input");
    else if(makeargv(s," \t", &chargv) <=0)
        fprintf(stderr,"Failed to parse command line \n");
    else{
        execvp(chargv[0],chargv);
        perror("Failed to execute command");
    }
    exit(1);
}



int parseandredirectin(char *cmd) {
    int error;
    int infd;
    char *infile;
    if((infile = strchr(cmd,'<')) == NULL)
        return 0;
    *infile = 0;
    infile = strtok(infile+1," \t");
    if(infile == NULL)
        return 0;
    if((infd = open(infile, O_RDONLY)) == -1)
        return -1;
    if(dup2(infd,STDIN_FILENO) == -1){
        error = errno;
        close(infd);
        errno = error;
        return -1;
    }
    return close(infd);
}

int parseandredirectout(char *cmd){
    int error;
    int outfd;
    char *outfile;
    if((outfile = strchr(cmd,'>')) == NULL)
        return 0;
    *outfile = 0;
    outfile = strtok(outfile +1," \t");
    if(outfile == NULL)
        return 0;
    if((outfd = open(outfile,FFLAG,FMODE)) == -1)
        return -1;
    if(dup2(outfd, STDOUT_FILENO) == -1) {
        error = errno;
        close(outfd);
        errno = error;
        return -1;
    }
    return close(outfd);
}

int makeargv(const char *s, const char *delimiters, char ***argvp){
    int error;
    int i;
    int numtokens;
    const char *snew;
    char *t;

    if((s == NULL) || (delimiters == NULL) || (argvp == NULL)){
        errno = EINVAL;
        return -1;
    }
    *argvp = NULL;
    snew = s+strspn(s,delimiters);
    if((t = malloc(strlen(snew) + 1)) == NULL)
        return -1;
    strcpy(t,snew);
    numtokens = 0;
    if(strtok(t,delimiters) != NULL)
        for(numtokens = 1; strtok(NULL,delimiters) != NULL;numtokens++);

    if((*argvp = malloc((numtokens +1)*sizeof(char *))) == NULL){
        error = errno;
        free(t);
        errno = error;
        return -1;
    }
    if(numtokens == 0)
        free(t);
    else{
        strcpy(t,snew);
        **argvp=strtok(t,delimiters);
        for(i=1;i<numtokens;i++)
            *((*argvp) + i) = strtok(NULL,delimiters);
    }
    *((*argvp) + numtokens) = NULL;
    return numtokens;
}


//문자열 자르기용 함수 
char* cut_string(int start,int end,char* str ,char* buffer){
    snprintf(buffer,end+1,"%s",str+start);
    return buffer;
}
